﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

namespace VntNet.PowerSchemeSwitcher
{
    class RegistryChangesNotificationHelper
    {
        
        internal enum HKeyEnum
        {
            LocalMachine
        }

        bool _exit;
        RegNotificationNative _native;
        internal event Action RegistryChanged;
        public RegistryChangesNotificationHelper(HKeyEnum keyEnum, string subKeyPath,bool watchSubTree)
	    {
            long hkey;
            switch (keyEnum)
            {
                case HKeyEnum.LocalMachine:
                    hkey = RegNotificationNative.HKEY_LOCAL_MACHINE;
                    break;
                default:
                    hkey = 0;
                    break;
            }
            _native = new RegNotificationNative(hkey, subKeyPath, watchSubTree);
            Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
            ThreadPool.QueueUserWorkItem(delegate { MonitorKeyFn(); });
	    }

        void Application_ApplicationExit(object sender, EventArgs e)
        {
            _exit = true;
        }

        void OnRegistryChanged()
        {
            if (RegistryChanged != null)
                RegistryChanged();
        }

        void MonitorKeyFn()
        {
            while (!_exit)
            {
                try
                {
                    _native.WaitForRegNotification();
                    OnRegistryChanged();
                }
                catch (Exception ex)
                {
                    Debug.Write("MonitorKeyFn:" + ex.ToString());
                    //throw;
                }
            }
        }

        class RegNotificationNative
        {

            #region native
            const int INFINITE = -1;
            internal const long HKEY_LOCAL_MACHINE = 0x80000002L;

            //Notify the caller if a subkey is added or deleted.
            const long REG_NOTIFY_CHANGE_NAME = 0x00000001L;

            //Notify the caller of changes to the attributes of the key, 
            //such as the security descriptor information.
            const long REG_NOTIFY_CHANGE_ATTRIBUTES = 0x00000002L;

            //Notify the caller of changes to a value of the key. 
            //This can include adding or deleting a value, or changing an existing value.
            const long REG_NOTIFY_CHANGE_LAST_SET = 0x00000004L;
            //Notify the caller of changes to the security descriptor of the key.
            const long REG_NOTIFY_CHANGE_SECURITY = 0x00000008L;


            [DllImport("kernel32.dll", EntryPoint = "CreateEvent")]
            static extern IntPtr CreateEvent(IntPtr eventAttributes,
                bool manualReset, bool initialState, String name);

            [DllImport("advapi32.dll", EntryPoint = "RegOpenKey")]
            static extern IntPtr RegOpenKey(IntPtr key, String subKey,
                out IntPtr resultSubKey);

            [DllImport("advapi32.dll", EntryPoint = "RegNotifyChangeKeyValue")]
            static extern long RegNotifyChangeKeyValue(IntPtr key,
                bool watchSubTree, int notifyFilter, IntPtr regEvent, bool
                async);

            [DllImport("kernel32.dll", EntryPoint = "WaitForSingleObject")]
            static extern long WaitForSingleObject(IntPtr handle, int timeOut);

            [DllImport("kernel32.dll", EntryPoint = "CloseHandle")]
            static extern IntPtr CloseHandle(IntPtr handle);
            #endregion
            IntPtr _monitoredKey;
            IntPtr _keyChangedEvent;
            bool _watchSubTree;
            internal event Action RegNotifyChangeKeyValueEvent;

            internal RegNotificationNative(long key, string subKey,bool watchSubTree)
            {
                _watchSubTree=watchSubTree;
                _keyChangedEvent = CreateEvent((IntPtr)null, false, false, null);
                unchecked
                {
                    //TODO:Check ret value
                    RegOpenKey(new IntPtr((int)key), subKey, out _monitoredKey);
                }
                CallRegNotifyChangeKeyValue();
            }

            private void CallRegNotifyChangeKeyValue()
            {
                //TODO:Check ret value
                long NotifyFilter = REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME;
                RegNotifyChangeKeyValue(_monitoredKey, _watchSubTree, (int)NotifyFilter, _keyChangedEvent, true);
            }

            internal void WaitForRegNotification()
            {
                WaitForSingleObject(_keyChangedEvent, INFINITE);
                //TODO:Check ret value
                RegNotifyChangeKeyValue();
                CallRegNotifyChangeKeyValue();
            }

            void RegNotifyChangeKeyValue()
            {
                if (RegNotifyChangeKeyValueEvent != null)
                    RegNotifyChangeKeyValueEvent();
            }
            //TODO:delete the key and the event
        }
    }
}
